home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / HTTP / Request.php < prev    next >
Encoding:
PHP Script  |  2007-12-20  |  46.0 KB  |  1,397 lines

  1. <?php
  2. // +-----------------------------------------------------------------------+
  3. // | Copyright (c) 2002-2003, Richard Heyes                                |
  4. // | All rights reserved.                                                  |
  5. // |                                                                       |
  6. // | Redistribution and use in source and binary forms, with or without    |
  7. // | modification, are permitted provided that the following conditions    |
  8. // | are met:                                                              |
  9. // |                                                                       |
  10. // | o Redistributions of source code must retain the above copyright      |
  11. // |   notice, this list of conditions and the following disclaimer.       |
  12. // | o Redistributions in binary form must reproduce the above copyright   |
  13. // |   notice, this list of conditions and the following disclaimer in the |
  14. // |   documentation and/or other materials provided with the distribution.|
  15. // | o The names of the authors may not be used to endorse or promote      |
  16. // |   products derived from this software without specific prior written  |
  17. // |   permission.                                                         |
  18. // |                                                                       |
  19. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
  20. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
  21. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  22. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
  23. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  24. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
  25. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  26. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  27. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
  28. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  29. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
  30. // |                                                                       |
  31. // +-----------------------------------------------------------------------+
  32. // | Author: Richard Heyes <richard@phpguru.org>                           |
  33. // +-----------------------------------------------------------------------+
  34. //
  35. // $Id: Request.php,v 1.51 2006/10/25 16:23:31 avb Exp $
  36. //
  37. // HTTP_Request Class
  38. //
  39. // Simple example, (Fetches yahoo.com and displays it):
  40. //
  41. // $a = &new HTTP_Request('http://www.yahoo.com/');
  42. // $a->sendRequest();
  43. // echo $a->getResponseBody();
  44. //
  45.  
  46. require_once 'PEAR.php';
  47. require_once 'Net/Socket.php';
  48. require_once 'Net/URL.php';
  49.  
  50. define('HTTP_REQUEST_METHOD_GET',     'GET',     true);
  51. define('HTTP_REQUEST_METHOD_HEAD',    'HEAD',    true);
  52. define('HTTP_REQUEST_METHOD_POST',    'POST',    true);
  53. define('HTTP_REQUEST_METHOD_PUT',     'PUT',     true);
  54. define('HTTP_REQUEST_METHOD_DELETE',  'DELETE',  true);
  55. define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
  56. define('HTTP_REQUEST_METHOD_TRACE',   'TRACE',   true);
  57.  
  58. define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
  59. define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
  60.  
  61. class HTTP_Request {
  62.  
  63.     /**
  64.     * Instance of Net_URL
  65.     * @var object Net_URL
  66.     */
  67.     var $_url;
  68.  
  69.     /**
  70.     * Type of request
  71.     * @var string
  72.     */
  73.     var $_method;
  74.  
  75.     /**
  76.     * HTTP Version
  77.     * @var string
  78.     */
  79.     var $_http;
  80.  
  81.     /**
  82.     * Request headers
  83.     * @var array
  84.     */
  85.     var $_requestHeaders;
  86.  
  87.     /**
  88.     * Basic Auth Username
  89.     * @var string
  90.     */
  91.     var $_user;
  92.     
  93.     /**
  94.     * Basic Auth Password
  95.     * @var string
  96.     */
  97.     var $_pass;
  98.  
  99.     /**
  100.     * Socket object
  101.     * @var object Net_Socket
  102.     */
  103.     var $_sock;
  104.     
  105.     /**
  106.     * Proxy server
  107.     * @var string
  108.     */
  109.     var $_proxy_host;
  110.     
  111.     /**
  112.     * Proxy port
  113.     * @var integer
  114.     */
  115.     var $_proxy_port;
  116.     
  117.     /**
  118.     * Proxy username
  119.     * @var string
  120.     */
  121.     var $_proxy_user;
  122.     
  123.     /**
  124.     * Proxy password
  125.     * @var string
  126.     */
  127.     var $_proxy_pass;
  128.  
  129.     /**
  130.     * Post data
  131.     * @var array
  132.     */
  133.     var $_postData;
  134.  
  135.    /**
  136.     * Request body  
  137.     * @var string
  138.     */
  139.     var $_body;
  140.  
  141.    /**
  142.     * A list of methods that MUST NOT have a request body, per RFC 2616
  143.     * @var array
  144.     */
  145.     var $_bodyDisallowed = array('TRACE');
  146.  
  147.    /**
  148.     * Files to post 
  149.     * @var array
  150.     */
  151.     var $_postFiles = array();
  152.  
  153.     /**
  154.     * Connection timeout.
  155.     * @var float
  156.     */
  157.     var $_timeout;
  158.     
  159.     /**
  160.     * HTTP_Response object
  161.     * @var object HTTP_Response
  162.     */
  163.     var $_response;
  164.     
  165.     /**
  166.     * Whether to allow redirects
  167.     * @var boolean
  168.     */
  169.     var $_allowRedirects;
  170.     
  171.     /**
  172.     * Maximum redirects allowed
  173.     * @var integer
  174.     */
  175.     var $_maxRedirects;
  176.     
  177.     /**
  178.     * Current number of redirects
  179.     * @var integer
  180.     */
  181.     var $_redirects;
  182.  
  183.    /**
  184.     * Whether to append brackets [] to array variables
  185.     * @var bool
  186.     */
  187.     var $_useBrackets = true;
  188.  
  189.    /**
  190.     * Attached listeners
  191.     * @var array
  192.     */
  193.     var $_listeners = array();
  194.  
  195.    /**
  196.     * Whether to save response body in response object property  
  197.     * @var bool
  198.     */
  199.     var $_saveBody = true;
  200.  
  201.    /**
  202.     * Timeout for reading from socket (array(seconds, microseconds))
  203.     * @var array
  204.     */
  205.     var $_readTimeout = null;
  206.  
  207.    /**
  208.     * Options to pass to Net_Socket::connect. See stream_context_create
  209.     * @var array
  210.     */
  211.     var $_socketOptions = null;
  212.  
  213.     /**
  214.     * Constructor
  215.     *
  216.     * Sets up the object
  217.     * @param    string  The url to fetch/access
  218.     * @param    array   Associative array of parameters which can have the following keys:
  219.     * <ul>
  220.     *   <li>method         - Method to use, GET, POST etc (string)</li>
  221.     *   <li>http           - HTTP Version to use, 1.0 or 1.1 (string)</li>
  222.     *   <li>user           - Basic Auth username (string)</li>
  223.     *   <li>pass           - Basic Auth password (string)</li>
  224.     *   <li>proxy_host     - Proxy server host (string)</li>
  225.     *   <li>proxy_port     - Proxy server port (integer)</li>
  226.     *   <li>proxy_user     - Proxy auth username (string)</li>
  227.     *   <li>proxy_pass     - Proxy auth password (string)</li>
  228.     *   <li>timeout        - Connection timeout in seconds (float)</li>
  229.     *   <li>allowRedirects - Whether to follow redirects or not (bool)</li>
  230.     *   <li>maxRedirects   - Max number of redirects to follow (integer)</li>
  231.     *   <li>useBrackets    - Whether to append [] to array variable names (bool)</li>
  232.     *   <li>saveBody       - Whether to save response body in response object property (bool)</li>
  233.     *   <li>readTimeout    - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
  234.     *   <li>socketOptions  - Options to pass to Net_Socket object (array)</li>
  235.     * </ul>
  236.     * @access public
  237.     */
  238.     function HTTP_Request($url = '', $params = array())
  239.     {
  240.         $this->_method         =  HTTP_REQUEST_METHOD_GET;
  241.         $this->_http           =  HTTP_REQUEST_HTTP_VER_1_1;
  242.         $this->_requestHeaders = array();
  243.         $this->_postData       = array();
  244.         $this->_body           = null;
  245.  
  246.         $this->_user = null;
  247.         $this->_pass = null;
  248.  
  249.         $this->_proxy_host = null;
  250.         $this->_proxy_port = null;
  251.         $this->_proxy_user = null;
  252.         $this->_proxy_pass = null;
  253.  
  254.         $this->_allowRedirects = false;
  255.         $this->_maxRedirects   = 3;
  256.         $this->_redirects      = 0;
  257.  
  258.         $this->_timeout  = null;
  259.         $this->_response = null;
  260.  
  261.         foreach ($params as $key => $value) {
  262.             $this->{'_' . $key} = $value;
  263.         }
  264.  
  265.         if (!empty($url)) {
  266.             $this->setURL($url);
  267.         }
  268.  
  269.         // Default useragent
  270.         $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
  271.  
  272.         // We don't do keep-alives by default
  273.         $this->addHeader('Connection', 'close');
  274.  
  275.         // Basic authentication
  276.         if (!empty($this->_user)) {
  277.             $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
  278.         }
  279.  
  280.         // Proxy authentication (see bug #5913)
  281.         if (!empty($this->_proxy_user)) {
  282.             $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
  283.         }
  284.  
  285.         // Use gzip encoding if possible
  286.         // Avoid gzip encoding if using multibyte functions (see #1781)
  287.         if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib') &&
  288.             0 == (2 & ini_get('mbstring.func_overload'))) {
  289.  
  290.             $this->addHeader('Accept-Encoding', 'gzip');
  291.         }
  292.     }
  293.     
  294.     /**
  295.     * Generates a Host header for HTTP/1.1 requests
  296.     *
  297.     * @access private
  298.     * @return string
  299.     */
  300.     function _generateHostHeader()
  301.     {
  302.         if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
  303.             $host = $this->_url->host . ':' . $this->_url->port;
  304.  
  305.         } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
  306.             $host = $this->_url->host . ':' . $this->_url->port;
  307.  
  308.         } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
  309.             $host = $this->_url->host . ':' . $this->_url->port;
  310.         
  311.         } else {
  312.             $host = $this->_url->host;
  313.         }
  314.  
  315.         return $host;
  316.     }
  317.     
  318.     /**
  319.     * Resets the object to its initial state (DEPRECATED).
  320.     * Takes the same parameters as the constructor.
  321.     *
  322.     * @param  string $url    The url to be requested
  323.     * @param  array  $params Associative array of parameters
  324.     *                        (see constructor for details)
  325.     * @access public
  326.     * @deprecated deprecated since 1.2, call the constructor if this is necessary
  327.     */
  328.     function reset($url, $params = array())
  329.     {
  330.         $this->HTTP_Request($url, $params);
  331.     }
  332.  
  333.     /**
  334.     * Sets the URL to be requested
  335.     *
  336.     * @param  string The url to be requested
  337.     * @access public
  338.     */
  339.     function setURL($url)
  340.     {
  341.         $this->_url = &new Net_URL($url, $this->_useBrackets);
  342.  
  343.         if (!empty($this->_url->user) || !empty($this->_url->pass)) {
  344.             $this->setBasicAuth($this->_url->user, $this->_url->pass);
  345.         }
  346.  
  347.         if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
  348.             $this->addHeader('Host', $this->_generateHostHeader());
  349.         }
  350.  
  351.         // set '/' instead of empty path rather than check later (see bug #8662)
  352.         if (empty($this->_url->path)) {
  353.             $this->_url->path = '/';
  354.         } 
  355.     }
  356.     
  357.    /**
  358.     * Returns the current request URL  
  359.     *
  360.     * @return   string  Current request URL
  361.     * @access   public
  362.     */
  363.     function getUrl($url)
  364.     {
  365.         return empty($this->_url)? '': $this->_url->getUrl();
  366.     }
  367.  
  368.     /**
  369.     * Sets a proxy to be used
  370.     *
  371.     * @param string     Proxy host
  372.     * @param int        Proxy port
  373.     * @param string     Proxy username
  374.     * @param string     Proxy password
  375.     * @access public
  376.     */
  377.     function setProxy($host, $port = 8080, $user = null, $pass = null)
  378.     {
  379.         $this->_proxy_host = $host;
  380.         $this->_proxy_port = $port;
  381.         $this->_proxy_user = $user;
  382.         $this->_proxy_pass = $pass;
  383.  
  384.         if (!empty($user)) {
  385.             $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  386.         }
  387.     }
  388.  
  389.     /**
  390.     * Sets basic authentication parameters
  391.     *
  392.     * @param string     Username
  393.     * @param string     Password
  394.     */
  395.     function setBasicAuth($user, $pass)
  396.     {
  397.         $this->_user = $user;
  398.         $this->_pass = $pass;
  399.  
  400.         $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  401.     }
  402.  
  403.     /**
  404.     * Sets the method to be used, GET, POST etc.
  405.     *
  406.     * @param string     Method to use. Use the defined constants for this
  407.     * @access public
  408.     */
  409.     function setMethod($method)
  410.     {
  411.         $this->_method = $method;
  412.     }
  413.  
  414.     /**
  415.     * Sets the HTTP version to use, 1.0 or 1.1
  416.     *
  417.     * @param string     Version to use. Use the defined constants for this
  418.     * @access public
  419.     */
  420.     function setHttpVer($http)
  421.     {
  422.         $this->_http = $http;
  423.     }
  424.  
  425.     /**
  426.     * Adds a request header
  427.     *
  428.     * @param string     Header name
  429.     * @param string     Header value
  430.     * @access public
  431.     */
  432.     function addHeader($name, $value)
  433.     {
  434.         $this->_requestHeaders[strtolower($name)] = $value;
  435.     }
  436.  
  437.     /**
  438.     * Removes a request header
  439.     *
  440.     * @param string     Header name to remove
  441.     * @access public
  442.     */
  443.     function removeHeader($name)
  444.     {
  445.         if (isset($this->_requestHeaders[strtolower($name)])) {
  446.             unset($this->_requestHeaders[strtolower($name)]);
  447.         }
  448.     }
  449.  
  450.     /**
  451.     * Adds a querystring parameter
  452.     *
  453.     * @param string     Querystring parameter name
  454.     * @param string     Querystring parameter value
  455.     * @param bool       Whether the value is already urlencoded or not, default = not
  456.     * @access public
  457.     */
  458.     function addQueryString($name, $value, $preencoded = false)
  459.     {
  460.         $this->_url->addQueryString($name, $value, $preencoded);
  461.     }    
  462.     
  463.     /**
  464.     * Sets the querystring to literally what you supply
  465.     *
  466.     * @param string     The querystring data. Should be of the format foo=bar&x=y etc
  467.     * @param bool       Whether data is already urlencoded or not, default = already encoded
  468.     * @access public
  469.     */
  470.     function addRawQueryString($querystring, $preencoded = true)
  471.     {
  472.         $this->_url->addRawQueryString($querystring, $preencoded);
  473.     }
  474.  
  475.     /**
  476.     * Adds postdata items
  477.     *
  478.     * @param string     Post data name
  479.     * @param string     Post data value
  480.     * @param bool       Whether data is already urlencoded or not, default = not
  481.     * @access public
  482.     */
  483.     function addPostData($name, $value, $preencoded = false)
  484.     {
  485.         if ($preencoded) {
  486.             $this->_postData[$name] = $value;
  487.         } else {
  488.             $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
  489.         }
  490.     }
  491.  
  492.    /**
  493.     * Recursively applies the callback function to the value
  494.     * 
  495.     * @param    mixed   Callback function
  496.     * @param    mixed   Value to process
  497.     * @access   private
  498.     * @return   mixed   Processed value
  499.     */
  500.     function _arrayMapRecursive($callback, $value)
  501.     {
  502.         if (!is_array($value)) {
  503.             return call_user_func($callback, $value);
  504.         } else {
  505.             $map = array();
  506.             foreach ($value as $k => $v) {
  507.                 $map[$k] = $this->_arrayMapRecursive($callback, $v);
  508.             }
  509.             return $map;
  510.         }
  511.     }
  512.  
  513.    /**
  514.     * Adds a file to upload
  515.     * 
  516.     * This also changes content-type to 'multipart/form-data' for proper upload
  517.     * 
  518.     * @access public
  519.     * @param  string    name of file-upload field
  520.     * @param  mixed     file name(s)
  521.     * @param  mixed     content-type(s) of file(s) being uploaded
  522.     * @return bool      true on success
  523.     * @throws PEAR_Error
  524.     */
  525.     function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
  526.     {
  527.         if (!is_array($fileName) && !is_readable($fileName)) {
  528.             return PEAR::raiseError("File '{$fileName}' is not readable");
  529.         } elseif (is_array($fileName)) {
  530.             foreach ($fileName as $name) {
  531.                 if (!is_readable($name)) {
  532.                     return PEAR::raiseError("File '{$name}' is not readable");
  533.                 }
  534.             }
  535.         }
  536.         $this->addHeader('Content-Type', 'multipart/form-data');
  537.         $this->_postFiles[$inputName] = array(
  538.             'name' => $fileName,
  539.             'type' => $contentType
  540.         );
  541.         return true;
  542.     }
  543.  
  544.     /**
  545.     * Adds raw postdata (DEPRECATED)
  546.     *
  547.     * @param string     The data
  548.     * @param bool       Whether data is preencoded or not, default = already encoded
  549.     * @access public
  550.     * @deprecated       deprecated since 1.3.0, method setBody() should be used instead
  551.     */
  552.     function addRawPostData($postdata, $preencoded = true)
  553.     {
  554.         $this->_body = $preencoded ? $postdata : urlencode($postdata);
  555.     }
  556.  
  557.    /**
  558.     * Sets the request body (for POST, PUT and similar requests)
  559.     *
  560.     * @param    string  Request body
  561.     * @access   public
  562.     */
  563.     function setBody($body)
  564.     {
  565.         $this->_body = $body;
  566.     }
  567.  
  568.     /**
  569.     * Clears any postdata that has been added (DEPRECATED). 
  570.     * 
  571.     * Useful for multiple request scenarios.
  572.     *
  573.     * @access public
  574.     * @deprecated deprecated since 1.2
  575.     */
  576.     function clearPostData()
  577.     {
  578.         $this->_postData = null;
  579.     }
  580.  
  581.     /**
  582.     * Appends a cookie to "Cookie:" header
  583.     * 
  584.     * @param string $name cookie name
  585.     * @param string $value cookie value
  586.     * @access public
  587.     */
  588.     function addCookie($name, $value)
  589.     {
  590.         $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
  591.         $this->addHeader('Cookie', $cookies . $name . '=' . $value);
  592.     }
  593.     
  594.     /**
  595.     * Clears any cookies that have been added (DEPRECATED). 
  596.     * 
  597.     * Useful for multiple request scenarios
  598.     *
  599.     * @access public
  600.     * @deprecated deprecated since 1.2
  601.     */
  602.     function clearCookies()
  603.     {
  604.         $this->removeHeader('Cookie');
  605.     }
  606.  
  607.     /**
  608.     * Sends the request
  609.     *
  610.     * @access public
  611.     * @param  bool   Whether to store response body in Response object property,
  612.     *                set this to false if downloading a LARGE file and using a Listener
  613.     * @return mixed  PEAR error on error, true otherwise
  614.     */
  615.     function sendRequest($saveBody = true)
  616.     {
  617.         if (!is_a($this->_url, 'Net_URL')) {
  618.             return PEAR::raiseError('No URL given.');
  619.         }
  620.  
  621.         $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
  622.         $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
  623.  
  624.         // 4.3.0 supports SSL connections using OpenSSL. The function test determines
  625.         // we running on at least 4.3.0
  626.         if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) {
  627.             if (isset($this->_proxy_host)) {
  628.                 return PEAR::raiseError('HTTPS proxies are not supported.');
  629.             }
  630.             $host = 'ssl://' . $host;
  631.         }
  632.  
  633.         // magic quotes may fuck up file uploads and chunked response processing
  634.         $magicQuotes = ini_get('magic_quotes_runtime');
  635.         ini_set('magic_quotes_runtime', false);
  636.  
  637.         // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive 
  638.         // connection token to a proxy server...
  639.         if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
  640.             'Keep-Alive' == $this->_requestHeaders['connection'])
  641.         {
  642.             $this->removeHeader('connection');
  643.         }
  644.  
  645.         $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
  646.                      (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
  647.         $sockets   = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
  648.         $sockKey   = $host . ':' . $port;
  649.         unset($this->_sock);
  650.  
  651.         // There is a connected socket in the "static" property?
  652.         if ($keepAlive && !empty($sockets[$sockKey]) &&
  653.             !empty($sockets[$sockKey]->fp)) 
  654.         {
  655.             $this->_sock =& $sockets[$sockKey];
  656.             $err = null;
  657.         } else {
  658.             $this->_notify('connect');
  659.             $this->_sock =& new Net_Socket();
  660.             $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
  661.         }
  662.         PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
  663.  
  664.         if (!PEAR::isError($err)) {
  665.             if (!empty($this->_readTimeout)) {
  666.                 $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
  667.             }
  668.  
  669.             $this->_notify('sentRequest');
  670.  
  671.             // Read the response
  672.             $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
  673.             $err = $this->_response->process(
  674.                 $this->_saveBody && $saveBody,
  675.                 HTTP_REQUEST_METHOD_HEAD != $this->_method
  676.             );
  677.  
  678.             if ($keepAlive) {
  679.                 $keepAlive = (isset($this->_response->_headers['content-length'])
  680.                               || (isset($this->_response->_headers['transfer-encoding'])
  681.                                   && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
  682.                 if ($keepAlive) {
  683.                     if (isset($this->_response->_headers['connection'])) {
  684.                         $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
  685.                     } else {
  686.                         $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
  687.                     }
  688.                 }
  689.             }
  690.         }
  691.  
  692.         ini_set('magic_quotes_runtime', $magicQuotes);
  693.  
  694.         if (PEAR::isError($err)) {
  695.             return $err;
  696.         }
  697.  
  698.         if (!$keepAlive) {
  699.             $this->disconnect();
  700.         // Store the connected socket in "static" property
  701.         } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
  702.             $sockets[$sockKey] =& $this->_sock;
  703.         }
  704.  
  705.         // Check for redirection
  706.         if (    $this->_allowRedirects
  707.             AND $this->_redirects <= $this->_maxRedirects
  708.             AND $this->getResponseCode() > 300
  709.             AND $this->getResponseCode() < 399
  710.             AND !empty($this->_response->_headers['location'])) {
  711.  
  712.             
  713.             $redirect = $this->_response->_headers['location'];
  714.  
  715.             // Absolute URL
  716.             if (preg_match('/^https?:\/\//i', $redirect)) {
  717.                 $this->_url = &new Net_URL($redirect);
  718.                 $this->addHeader('Host', $this->_generateHostHeader());
  719.             // Absolute path
  720.             } elseif ($redirect{0} == '/') {
  721.                 $this->_url->path = $redirect;
  722.             
  723.             // Relative path
  724.             } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
  725.                 if (substr($this->_url->path, -1) == '/') {
  726.                     $redirect = $this->_url->path . $redirect;
  727.                 } else {
  728.                     $redirect = dirname($this->_url->path) . '/' . $redirect;
  729.                 }
  730.                 $redirect = Net_URL::resolvePath($redirect);
  731.                 $this->_url->path = $redirect;
  732.                 
  733.             // Filename, no path
  734.             } else {
  735.                 if (substr($this->_url->path, -1) == '/') {
  736.                     $redirect = $this->_url->path . $redirect;
  737.                 } else {
  738.                     $redirect = dirname($this->_url->path) . '/' . $redirect;
  739.                 }
  740.                 $this->_url->path = $redirect;
  741.             }
  742.  
  743.             $this->_redirects++;
  744.             return $this->sendRequest($saveBody);
  745.  
  746.         // Too many redirects
  747.         } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
  748.             return PEAR::raiseError('Too many redirects');
  749.         }
  750.  
  751.         return true;
  752.     }
  753.  
  754.     /**
  755.      * Disconnect the socket, if connected. Only useful if using Keep-Alive.
  756.      *
  757.      * @access public
  758.      */
  759.     function disconnect()
  760.     {
  761.         if (!empty($this->_sock) && !empty($this->_sock->fp)) {
  762.             $this->_notify('disconnect');
  763.             $this->_sock->disconnect();
  764.         }
  765.     }
  766.  
  767.     /**
  768.     * Returns the response code
  769.     *
  770.     * @access public
  771.     * @return mixed     Response code, false if not set
  772.     */
  773.     function getResponseCode()
  774.     {
  775.         return isset($this->_response->_code) ? $this->_response->_code : false;
  776.     }
  777.  
  778.     /**
  779.     * Returns either the named header or all if no name given
  780.     *
  781.     * @access public
  782.     * @param string     The header name to return, do not set to get all headers
  783.     * @return mixed     either the value of $headername (false if header is not present)
  784.     *                   or an array of all headers
  785.     */
  786.     function getResponseHeader($headername = null)
  787.     {
  788.         if (!isset($headername)) {
  789.             return isset($this->_response->_headers)? $this->_response->_headers: array();
  790.         } else {
  791.             $headername = strtolower($headername);
  792.             return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
  793.         }
  794.     }
  795.  
  796.     /**
  797.     * Returns the body of the response
  798.     *
  799.     * @access public
  800.     * @return mixed     response body, false if not set
  801.     */
  802.     function getResponseBody()
  803.     {
  804.         return isset($this->_response->_body) ? $this->_response->_body : false;
  805.     }
  806.  
  807.     /**
  808.     * Returns cookies set in response
  809.     * 
  810.     * @access public
  811.     * @return mixed     array of response cookies, false if none are present
  812.     */
  813.     function getResponseCookies()
  814.     {
  815.         return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
  816.     }
  817.  
  818.     /**
  819.     * Builds the request string
  820.     *
  821.     * @access private
  822.     * @return string The request string
  823.     */
  824.     function _buildRequest()
  825.     {
  826.         $separator = ini_get('arg_separator.output');
  827.         ini_set('arg_separator.output', '&');
  828.         $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
  829.         ini_set('arg_separator.output', $separator);
  830.  
  831.         $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
  832.         $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
  833.         $path = $this->_url->path . $querystring;
  834.         $url  = $host . $port . $path;
  835.  
  836.         $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
  837.  
  838.         if (in_array($this->_method, $this->_bodyDisallowed) ||
  839.             (empty($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
  840.              (empty($this->_postData) && empty($this->_postFiles)))))
  841.         {
  842.             $this->removeHeader('Content-Type');
  843.         } else {
  844.             if (empty($this->_requestHeaders['content-type'])) {
  845.                 // Add default content-type
  846.                 $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
  847.             } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
  848.                 $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
  849.                 $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
  850.             }
  851.         }
  852.  
  853.         // Request Headers
  854.         if (!empty($this->_requestHeaders)) {
  855.             foreach ($this->_requestHeaders as $name => $value) {
  856.                 $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
  857.                 $request      .= $canonicalName . ': ' . $value . "\r\n";
  858.             }
  859.         }
  860.  
  861.         // No post data or wrong method, so simply add a final CRLF
  862.         if (in_array($this->_method, $this->_bodyDisallowed) || 
  863.             (HTTP_REQUEST_METHOD_POST != $this->_method && empty($this->_body))) {
  864.  
  865.             $request .= "\r\n";
  866.  
  867.         // Post data if it's an array
  868.         } elseif (HTTP_REQUEST_METHOD_POST == $this->_method && 
  869.                   (!empty($this->_postData) || !empty($this->_postFiles))) {
  870.  
  871.             // "normal" POST request
  872.             if (!isset($boundary)) {
  873.                 $postdata = implode('&', array_map(
  874.                     create_function('$a', 'return $a[0] . \'=\' . $a[1];'), 
  875.                     $this->_flattenArray('', $this->_postData)
  876.                 ));
  877.  
  878.             // multipart request, probably with file uploads
  879.             } else {
  880.                 $postdata = '';
  881.                 if (!empty($this->_postData)) {
  882.                     $flatData = $this->_flattenArray('', $this->_postData);
  883.                     foreach ($flatData as $item) {
  884.                         $postdata .= '--' . $boundary . "\r\n";
  885.                         $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
  886.                         $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
  887.                     }
  888.                 }
  889.                 foreach ($this->_postFiles as $name => $value) {
  890.                     if (is_array($value['name'])) {
  891.                         $varname       = $name . ($this->_useBrackets? '[]': '');
  892.                     } else {
  893.                         $varname       = $name;
  894.                         $value['name'] = array($value['name']);
  895.                     }
  896.                     foreach ($value['name'] as $key => $filename) {
  897.                         $fp   = fopen($filename, 'r');
  898.                         $data = fread($fp, filesize($filename));
  899.                         fclose($fp);
  900.                         $basename = basename($filename);
  901.                         $type     = is_array($value['type'])? @$value['type'][$key]: $value['type'];
  902.  
  903.                         $postdata .= '--' . $boundary . "\r\n";
  904.                         $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
  905.                         $postdata .= "\r\nContent-Type: " . $type;
  906.                         $postdata .= "\r\n\r\n" . $data . "\r\n";
  907.                     }
  908.                 }
  909.                 $postdata .= '--' . $boundary . "--\r\n";
  910.             }
  911.             $request .= 'Content-Length: ' . strlen($postdata) . "\r\n\r\n";
  912.             $request .= $postdata;
  913.  
  914.         // Explicitly set request body
  915.         } elseif (!empty($this->_body)) {
  916.  
  917.             $request .= 'Content-Length: ' . strlen($this->_body) . "\r\n\r\n";
  918.             $request .= $this->_body;
  919.         }
  920.         
  921.         return $request;
  922.     }
  923.  
  924.    /**
  925.     * Helper function to change the (probably multidimensional) associative array
  926.     * into the simple one.
  927.     *
  928.     * @param    string  name for item
  929.     * @param    mixed   item's values
  930.     * @return   array   array with the following items: array('item name', 'item value');
  931.     */
  932.     function _flattenArray($name, $values)
  933.     {
  934.         if (!is_array($values)) {
  935.             return array(array($name, $values));
  936.         } else {
  937.             $ret = array();
  938.             foreach ($values as $k => $v) {
  939.                 if (empty($name)) {
  940.                     $newName = $k;
  941.                 } elseif ($this->_useBrackets) {
  942.                     $newName = $name . '[' . $k . ']';
  943.                 } else {
  944.                     $newName = $name;
  945.                 }
  946.                 $ret = array_merge($ret, $this->_flattenArray($newName, $v));
  947.             }
  948.             return $ret;
  949.         }
  950.     }
  951.  
  952.  
  953.    /**
  954.     * Adds a Listener to the list of listeners that are notified of
  955.     * the object's events
  956.     * 
  957.     * @param    object   HTTP_Request_Listener instance to attach
  958.     * @return   boolean  whether the listener was successfully attached
  959.     * @access   public
  960.     */
  961.     function attach(&$listener)
  962.     {
  963.         if (!is_a($listener, 'HTTP_Request_Listener')) {
  964.             return false;
  965.         }
  966.         $this->_listeners[$listener->getId()] =& $listener;
  967.         return true;
  968.     }
  969.  
  970.  
  971.    /**
  972.     * Removes a Listener from the list of listeners 
  973.     * 
  974.     * @param    object   HTTP_Request_Listener instance to detach
  975.     * @return   boolean  whether the listener was successfully detached
  976.     * @access   public
  977.     */
  978.     function detach(&$listener)
  979.     {
  980.         if (!is_a($listener, 'HTTP_Request_Listener') || 
  981.             !isset($this->_listeners[$listener->getId()])) {
  982.             return false;
  983.         }
  984.         unset($this->_listeners[$listener->getId()]);
  985.         return true;
  986.     }
  987.  
  988.  
  989.    /**
  990.     * Notifies all registered listeners of an event.
  991.     * 
  992.     * Events sent by HTTP_Request object
  993.     * - 'connect': on connection to server
  994.     * - 'sentRequest': after the request was sent
  995.     * - 'disconnect': on disconnection from server
  996.     * 
  997.     * Events sent by HTTP_Response object
  998.     * - 'gotHeaders': after receiving response headers (headers are passed in $data)
  999.     * - 'tick': on receiving a part of response body (the part is passed in $data)
  1000.     * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)
  1001.     * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
  1002.     * 
  1003.     * @param    string  Event name
  1004.     * @param    mixed   Additional data
  1005.     * @access   private
  1006.     */
  1007.     function _notify($event, $data = null)
  1008.     {
  1009.         foreach (array_keys($this->_listeners) as $id) {
  1010.             $this->_listeners[$id]->update($this, $event, $data);
  1011.         }
  1012.     }
  1013. }
  1014.  
  1015.  
  1016. /**
  1017. * Response class to complement the Request class
  1018. */
  1019. class HTTP_Response
  1020. {
  1021.     /**
  1022.     * Socket object
  1023.     * @var object
  1024.     */
  1025.     var $_sock;
  1026.  
  1027.     /**
  1028.     * Protocol
  1029.     * @var string
  1030.     */
  1031.     var $_protocol;
  1032.     
  1033.     /**
  1034.     * Return code
  1035.     * @var string
  1036.     */
  1037.     var $_code;
  1038.     
  1039.     /**
  1040.     * Response headers
  1041.     * @var array
  1042.     */
  1043.     var $_headers;
  1044.  
  1045.     /**
  1046.     * Cookies set in response  
  1047.     * @var array
  1048.     */
  1049.     var $_cookies;
  1050.  
  1051.     /**
  1052.     * Response body
  1053.     * @var string
  1054.     */
  1055.     var $_body = '';
  1056.  
  1057.    /**
  1058.     * Used by _readChunked(): remaining length of the current chunk
  1059.     * @var string
  1060.     */
  1061.     var $_chunkLength = 0;
  1062.  
  1063.    /**
  1064.     * Attached listeners
  1065.     * @var array
  1066.     */
  1067.     var $_listeners = array();
  1068.  
  1069.    /**
  1070.     * Bytes left to read from message-body
  1071.     * @var null|int
  1072.     */
  1073.     var $_toRead;
  1074.  
  1075.     /**
  1076.     * Constructor
  1077.     *
  1078.     * @param  object Net_Socket     socket to read the response from
  1079.     * @param  array                 listeners attached to request
  1080.     * @return mixed PEAR Error on error, true otherwise
  1081.     */
  1082.     function HTTP_Response(&$sock, &$listeners)
  1083.     {
  1084.         $this->_sock      =& $sock;
  1085.         $this->_listeners =& $listeners;
  1086.     }
  1087.  
  1088.  
  1089.    /**
  1090.     * Processes a HTTP response
  1091.     * 
  1092.     * This extracts response code, headers, cookies and decodes body if it 
  1093.     * was encoded in some way
  1094.     *
  1095.     * @access public
  1096.     * @param  bool      Whether to store response body in object property, set
  1097.     *                   this to false if downloading a LARGE file and using a Listener.
  1098.     *                   This is assumed to be true if body is gzip-encoded.
  1099.     * @param  bool      Whether the response can actually have a message-body.
  1100.     *                   Will be set to false for HEAD requests.
  1101.     * @throws PEAR_Error
  1102.     * @return mixed     true on success, PEAR_Error in case of malformed response
  1103.     */
  1104.     function process($saveBody = true, $canHaveBody = true)
  1105.     {
  1106.         do {
  1107.             $line = $this->_sock->readLine();
  1108.             if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) {
  1109.                 return PEAR::raiseError('Malformed response.');
  1110.             } else {
  1111.                 $this->_protocol = 'HTTP/' . $http_version;
  1112.                 $this->_code     = intval($returncode);
  1113.             }
  1114.             while ('' !== ($header = $this->_sock->readLine())) {
  1115.                 $this->_processHeader($header);
  1116.             }
  1117.         } while (100 == $this->_code);
  1118.  
  1119.         $this->_notify('gotHeaders', $this->_headers);
  1120.  
  1121.         // RFC 2616, section 4.4:
  1122.         // 1. Any response message which "MUST NOT" include a message-body ... 
  1123.         // is always terminated by the first empty line after the header fields 
  1124.         // 3. ... If a message is received with both a
  1125.         // Transfer-Encoding header field and a Content-Length header field,
  1126.         // the latter MUST be ignored.
  1127.         $canHaveBody = $canHaveBody && $this->_code >= 200 && 
  1128.                        $this->_code != 204 && $this->_code != 304;
  1129.  
  1130.         // If response body is present, read it and decode
  1131.         $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
  1132.         $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
  1133.         $hasBody = false;
  1134.         if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) || 
  1135.                 0 != $this->_headers['content-length']))
  1136.         {
  1137.             if ($chunked || !isset($this->_headers['content-length'])) {
  1138.                 $this->_toRead = null;
  1139.             } else {
  1140.                 $this->_toRead = $this->_headers['content-length'];
  1141.             }
  1142.             while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
  1143.                 if ($chunked) {
  1144.                     $data = $this->_readChunked();
  1145.                 } elseif (is_null($this->_toRead)) {
  1146.                     $data = $this->_sock->read(4096);
  1147.                 } else {
  1148.                     $data = $this->_sock->read(min(4096, $this->_toRead));
  1149.                     $this->_toRead -= strlen($data);
  1150.                 }
  1151.                 if ('' == $data) {
  1152.                     break;
  1153.                 } else {
  1154.                     $hasBody = true;
  1155.                     if ($saveBody || $gzipped) {
  1156.                         $this->_body .= $data;
  1157.                     }
  1158.                     $this->_notify($gzipped? 'gzTick': 'tick', $data);
  1159.                 }
  1160.             }
  1161.         }
  1162.  
  1163.         if ($hasBody) {
  1164.             // Uncompress the body if needed
  1165.             if ($gzipped) {
  1166.                 $body = $this->_decodeGzip($this->_body);
  1167.                 if (PEAR::isError($body)) {
  1168.                     return $body;
  1169.                 }
  1170.                 $this->_body = $body;
  1171.                 $this->_notify('gotBody', $this->_body);
  1172.             } else {
  1173.                 $this->_notify('gotBody');
  1174.             }
  1175.         }
  1176.         return true;
  1177.     }
  1178.  
  1179.  
  1180.    /**
  1181.     * Processes the response header
  1182.     *
  1183.     * @access private
  1184.     * @param  string    HTTP header
  1185.     */
  1186.     function _processHeader($header)
  1187.     {
  1188.         if (false === strpos($header, ':')) {
  1189.             return;
  1190.         }
  1191.         list($headername, $headervalue) = explode(':', $header, 2);
  1192.         $headername  = strtolower($headername);
  1193.         $headervalue = ltrim($headervalue);
  1194.         
  1195.         if ('set-cookie' != $headername) {
  1196.             if (isset($this->_headers[$headername])) {
  1197.                 $this->_headers[$headername] .= ',' . $headervalue;
  1198.             } else {
  1199.                 $this->_headers[$headername]  = $headervalue;
  1200.             }
  1201.         } else {
  1202.             $this->_parseCookie($headervalue);
  1203.         }
  1204.     }
  1205.  
  1206.  
  1207.    /**
  1208.     * Parse a Set-Cookie header to fill $_cookies array
  1209.     *
  1210.     * @access private
  1211.     * @param  string    value of Set-Cookie header
  1212.     */
  1213.     function _parseCookie($headervalue)
  1214.     {
  1215.         $cookie = array(
  1216.             'expires' => null,
  1217.             'domain'  => null,
  1218.             'path'    => null,
  1219.             'secure'  => false
  1220.         );
  1221.  
  1222.         // Only a name=value pair
  1223.         if (!strpos($headervalue, ';')) {
  1224.             $pos = strpos($headervalue, '=');
  1225.             $cookie['name']  = trim(substr($headervalue, 0, $pos));
  1226.             $cookie['value'] = trim(substr($headervalue, $pos + 1));
  1227.  
  1228.         // Some optional parameters are supplied
  1229.         } else {
  1230.             $elements = explode(';', $headervalue);
  1231.             $pos = strpos($elements[0], '=');
  1232.             $cookie['name']  = trim(substr($elements[0], 0, $pos));
  1233.             $cookie['value'] = trim(substr($elements[0], $pos + 1));
  1234.  
  1235.             for ($i = 1; $i < count($elements); $i++) {
  1236.                 if (false === strpos($elements[$i], '=')) {
  1237.                     $elName  = trim($elements[$i]);
  1238.                     $elValue = null;
  1239.                 } else {
  1240.                     list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
  1241.                 }
  1242.                 $elName = strtolower($elName);
  1243.                 if ('secure' == $elName) {
  1244.                     $cookie['secure'] = true;
  1245.                 } elseif ('expires' == $elName) {
  1246.                     $cookie['expires'] = str_replace('"', '', $elValue);
  1247.                 } elseif ('path' == $elName || 'domain' == $elName) {
  1248.                     $cookie[$elName] = urldecode($elValue);
  1249.                 } else {
  1250.                     $cookie[$elName] = $elValue;
  1251.                 }
  1252.             }
  1253.         }
  1254.         $this->_cookies[] = $cookie;
  1255.     }
  1256.  
  1257.  
  1258.    /**
  1259.     * Read a part of response body encoded with chunked Transfer-Encoding
  1260.     * 
  1261.     * @access private
  1262.     * @return string
  1263.     */
  1264.     function _readChunked()
  1265.     {
  1266.         // at start of the next chunk?
  1267.         if (0 == $this->_chunkLength) {
  1268.             $line = $this->_sock->readLine();
  1269.             if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
  1270.                 $this->_chunkLength = hexdec($matches[1]); 
  1271.                 // Chunk with zero length indicates the end
  1272.                 if (0 == $this->_chunkLength) {
  1273.                     $this->_sock->readLine(); // make this an eof()
  1274.                     return '';
  1275.                 }
  1276.             } else {
  1277.                 return '';
  1278.             }
  1279.         }
  1280.         $data = $this->_sock->read($this->_chunkLength);
  1281.         $this->_chunkLength -= strlen($data);
  1282.         if (0 == $this->_chunkLength) {
  1283.             $this->_sock->readLine(); // Trailing CRLF
  1284.         }
  1285.         return $data;
  1286.     }
  1287.  
  1288.  
  1289.    /**
  1290.     * Notifies all registered listeners of an event.
  1291.     * 
  1292.     * @param    string  Event name
  1293.     * @param    mixed   Additional data
  1294.     * @access   private
  1295.     * @see HTTP_Request::_notify()
  1296.     */
  1297.     function _notify($event, $data = null)
  1298.     {
  1299.         foreach (array_keys($this->_listeners) as $id) {
  1300.             $this->_listeners[$id]->update($this, $event, $data);
  1301.         }
  1302.     }
  1303.  
  1304.  
  1305.    /**
  1306.     * Decodes the message-body encoded by gzip
  1307.     *
  1308.     * The real decoding work is done by gzinflate() built-in function, this
  1309.     * method only parses the header and checks data for compliance with
  1310.     * RFC 1952  
  1311.     *
  1312.     * @access   private
  1313.     * @param    string  gzip-encoded data
  1314.     * @return   string  decoded data
  1315.     */
  1316.     function _decodeGzip($data)
  1317.     {
  1318.         $length = strlen($data);
  1319.         // If it doesn't look like gzip-encoded data, don't bother
  1320.         if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
  1321.             return $data;
  1322.         }
  1323.         $method = ord(substr($data, 2, 1));
  1324.         if (8 != $method) {
  1325.             return PEAR::raiseError('_decodeGzip(): unknown compression method');
  1326.         }
  1327.         $flags = ord(substr($data, 3, 1));
  1328.         if ($flags & 224) {
  1329.             return PEAR::raiseError('_decodeGzip(): reserved bits are set');
  1330.         }
  1331.  
  1332.         // header is 10 bytes minimum. may be longer, though.
  1333.         $headerLength = 10;
  1334.         // extra fields, need to skip 'em
  1335.         if ($flags & 4) {
  1336.             if ($length - $headerLength - 2 < 8) {
  1337.                 return PEAR::raiseError('_decodeGzip(): data too short');
  1338.             }
  1339.             $extraLength = unpack('v', substr($data, 10, 2));
  1340.             if ($length - $headerLength - 2 - $extraLength[1] < 8) {
  1341.                 return PEAR::raiseError('_decodeGzip(): data too short');
  1342.             }
  1343.             $headerLength += $extraLength[1] + 2;
  1344.         }
  1345.         // file name, need to skip that
  1346.         if ($flags & 8) {
  1347.             if ($length - $headerLength - 1 < 8) {
  1348.                 return PEAR::raiseError('_decodeGzip(): data too short');
  1349.             }
  1350.             $filenameLength = strpos(substr($data, $headerLength), chr(0));
  1351.             if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
  1352.                 return PEAR::raiseError('_decodeGzip(): data too short');
  1353.             }
  1354.             $headerLength += $filenameLength + 1;
  1355.         }
  1356.         // comment, need to skip that also
  1357.         if ($flags & 16) {
  1358.             if ($length - $headerLength - 1 < 8) {
  1359.                 return PEAR::raiseError('_decodeGzip(): data too short');
  1360.             }
  1361.             $commentLength = strpos(substr($data, $headerLength), chr(0));
  1362.             if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
  1363.                 return PEAR::raiseError('_decodeGzip(): data too short');
  1364.             }
  1365.             $headerLength += $commentLength + 1;
  1366.         }
  1367.         // have a CRC for header. let's check
  1368.         if ($flags & 1) {
  1369.             if ($length - $headerLength - 2 < 8) {
  1370.                 return PEAR::raiseError('_decodeGzip(): data too short');
  1371.             }
  1372.             $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));
  1373.             $crcStored = unpack('v', substr($data, $headerLength, 2));
  1374.             if ($crcReal != $crcStored[1]) {
  1375.                 return PEAR::raiseError('_decodeGzip(): header CRC check failed');
  1376.             }
  1377.             $headerLength += 2;
  1378.         }
  1379.         // unpacked data CRC and size at the end of encoded data
  1380.         $tmp = unpack('V2', substr($data, -8));
  1381.         $dataCrc  = $tmp[1];
  1382.         $dataSize = $tmp[2];
  1383.  
  1384.         // finally, call the gzinflate() function
  1385.         $unpacked = @gzinflate(substr($data, $headerLength, -8), $dataSize);
  1386.         if (false === $unpacked) {
  1387.             return PEAR::raiseError('_decodeGzip(): gzinflate() call failed');
  1388.         } elseif ($dataSize != strlen($unpacked)) {
  1389.             return PEAR::raiseError('_decodeGzip(): data size check failed');
  1390.         } elseif ($dataCrc != crc32($unpacked)) {
  1391.             return PEAR::raiseError('_decodeGzip(): data CRC check failed');
  1392.         }
  1393.         return $unpacked;
  1394.     }
  1395. } // End class HTTP_Response
  1396. ?>
  1397.